/**
 * 
 */
package truerefactor.refactor;

import java.util.ArrayList;
import java.util.List;

import truerefactor.graph.Accessibility;
import truerefactor.graph.AttributeNode;
import truerefactor.graph.ClassNode;
import truerefactor.graph.CodeGraph;
import truerefactor.graph.CodeNode;
import truerefactor.graph.Edge;
import truerefactor.graph.EdgeType;
import truerefactor.graph.MethodNode;

/**
 * PullUpField - An Implementation of the Pull Up Field Refactoring Technique
 * 
 * @author Isaac Griffith
 */
public class PullUpField extends Refactoring {

    /**
     * Creates a new PullUpField instance
     */
    public PullUpField()
    {
        this.name = "Pull-up Field";
        this.description = "";
    }

    /*
     * (non-Javadoc)
     * @see
     * truerefactor.refactor.Refactoring#execute(truerefactor.graph.CodeNode [])
     */
    @Override
    public void execute(CodeGraph graph)
    {
        if (operands.isEmpty())
            return;

        AttributeNode field = (AttributeNode) operands.get(0);
        ClassNode parent = field.getParent();

        if (parent != null) // FIXME: Should never be null
        {
            List<ClassNode> possibleSupers = parent.getParentClasses();
            ClassNode superClass = null;
            for (ClassNode node : possibleSupers)
            {
                if (graph.isEdge(node, parent, EdgeType.Generalization))
                {
                    superClass = (ClassNode) node;
                    break;
                }
            }

            if (superClass == null)
                return;

            // 1. Inspect all uses of the candidate fields to ensure they are
            // used
            // in the same way.

            // TODO worry about this when doing code generation

            // 2. If the fields do not have the same name, rename the fields so
            // that
            // they have the name you want to use for the superclass field

            // TODO for now only look for fields with same name, worry about the
            // rest of this during code generation

            List<ClassNode> subClasses = superClass.getSubClasses();

            // 3. Create a new field in the superclass
            // a. if the fields are private, you will need to protect the super
            // class field so that the subclasses can refer to it.
            AttributeNode newField = new AttributeNode(superClass, field.getIdentifier(), "");
            newField.setAccessibility(Accessibility.Protected);
            newField.setParent(superClass);
            
            for (CodeNode node : superClass.getAttributeByType(newField.getType())) {
                if (node.getIdentifier().equals(newField.getIdentifier()))
                    return;
            }
            
            superClass.addAttribute(newField);
            graph.insert(newField);
            graph.insert(new Edge(superClass, newField, EdgeType.Attribute));

            removeSubClassFields(graph, field, parent, subClasses, newField);

            decideOnSelfEncapsulation(graph, newField);
        }
    }

    private void decideOnSelfEncapsulation(CodeGraph graph, AttributeNode newField)
    {
        // 5. Consider using Self-Encapsulate Field on the new Field
        List<CodeNode> encapsulateOps = new ArrayList<CodeNode>();
        encapsulateOps.add(newField);

        SelfEncapsulateField encapsulate = new SelfEncapsulateField();
        encapsulate.setOperands(encapsulateOps);
        encapsulate.execute(graph);
    }

    private void removeSubClassFields(CodeGraph graph, AttributeNode field, ClassNode parent,
            List<ClassNode> subClasses, AttributeNode newField)
    {
        // 4. Delete the subclass fields
        for (CodeNode node : subClasses)
        {
            if (node instanceof ClassNode)
            {
                ClassNode subClass = (ClassNode) node;
                AttributeNode oldField = (AttributeNode) subClass.getAttribute(field.getIdentifier());

                for (MethodNode method : oldField.getAssociatedMethods())
                {
                    method.removeAssociatedAttribute(oldField);
                    method.addAssociatedAttribute(newField);
                }

                for (MethodNode method : oldField.getOtherMethods())
                {
                    method.removeOtherAttribute(oldField);
                    method.addOtherAttribute(newField);
                }

                if (!oldField.isPrimitive())
                {
                    if (graph.isEdge(parent, oldField.getAssociatedClass(), EdgeType.Association))
                    {
                        graph.remove(graph.getEdge(parent, oldField.getAssociatedClass(), EdgeType.Association));
                        graph.insert(new Edge(newField.getParent(), graph.getClass(newField.getType()),
                                EdgeType.Association));
                        parent.removeAssociatedClass(oldField.getAssociatedClass());
                    }
                }
                
                if (oldField != null)
                {
                    parent.removeAttribute(oldField);
                    graph.remove(graph.getEdge(parent, oldField, EdgeType.Attribute));
                    graph.remove(oldField);
                }
            }
        }
    }
}
